package schema

import (
	"amazegame/schema/dirmonitor"
	"encoding/csv"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/360EntSecGroup-Skylar/excelize"
	"github.com/extrame/xls"
	"github.com/forging2012/gogb2312"
	"github.com/mitchellh/mapstructure"
	"os"

	"io/fs"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
)

type reader func(table *MetaTable, file string) error
type MetaSheet struct {
	SheetName     string
	OriginFile    string
	TypeString    []string
	FieldString   []string
	CommentString []string
	sheetRows     map[string]map[string]any
	sheetMutex    sync.RWMutex
}

type TableOption struct {
	TupleSeparator string
	SliceSeparator string
	CommentHeader  string
}

type MetaTable struct {
	Option      TableOption
	Sheets      map[string]*MetaSheet
	SheetsMutex sync.RWMutex
	monitor     *dirmonitor.Monitor
	readers     map[string]reader
}

func NewHotTable(dir string, hotLoad bool, tpSeparator string, slSeparator string, cmtHeader string) (hotTable *MetaTable, err error) {

	hotTable = &MetaTable{
		Sheets: make(map[string]*MetaSheet),
		Option: TableOption{
			TupleSeparator: tpSeparator,
			SliceSeparator: slSeparator,
			CommentHeader:  cmtHeader,
		},
		readers: map[string]reader{
			".xls":  readXls,
			".xlsx": readXlsx,
			".xlsm": readXlsx,
			".json": readJson,
			".csv":  readCSV,
		},
	}

	if tpSeparator == slSeparator && tpSeparator != "" {
		return nil, errors.New("separator of tuple is same slice's")
	}

	if len(hotTable.Option.TupleSeparator) == 0 {
		hotTable.Option.TupleSeparator = ":"
	}
	if len(hotTable.Option.SliceSeparator) == 0 {
		hotTable.Option.SliceSeparator = ","
	}
	if len(hotTable.Option.CommentHeader) == 0 {
		hotTable.Option.CommentHeader = "#"
	}

	hotTable.readDir(dir)
	if hotLoad {
		hotTable.monitor, err = dirmonitor.NewMonitor(dir, func(File string, isDelete bool) {
			hotTable.Process(File, isDelete)
		})
		if err != nil {
			return
		}
	}

	return
}

func (table *MetaTable) readDir(dir string) bool {

	var files = make([]string, 0)
	err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
		if !d.IsDir() {
			ext := strings.ToLower(filepath.Ext(path))
			if _, ok := table.readers[ext]; ok {
				files = append(files, path)
			}
		}
		return nil
	})
	if err != nil {
		return false
	}

	for i := 0; i < len(files); i++ {
		table.Process(files[i], false)
	}

	return true
}

func (table *MetaTable) Process(file string, isDelete bool) bool {
	if isDelete {
		if table != nil {
			defer table.SheetsMutex.Lock()
			table.SheetsMutex.Lock()
			for tabName, sheet := range table.Sheets {
				if sheet.OriginFile == file {
					delete(table.Sheets, tabName)
				}
			}
		}
	}
	var err error

	ext := strings.ToLower(filepath.Ext(file))
	if reader, ok := table.readers[ext]; ok {
		err := reader(table, file)
		if err != nil {
			return false
		}
	}

	if err != nil {
		fmt.Println("can not read file :", err.Error())
		return false
	}
	return true
}

func readXls(table *MetaTable, file string) error {
	if xlFile, err := xls.Open(file, "utf-8"); err == nil {
		sheetCount := xlFile.NumSheets()
		for i := 0; i < sheetCount; i++ {
			if workSheet := xlFile.GetSheet(i); workSheet != nil {
				if workSheet.MaxRow != 0 {
					temp := make([][]string, workSheet.MaxRow)
					for i := 0; i < int(workSheet.MaxRow); i++ {
						row := workSheet.Row(i)
						data := make([]string, 0)
						if row.LastCol() > 0 {
							for j := 0; j < row.LastCol(); j++ {
								col := row.Col(j)
								data = append(data, col)
							}
						}
						temp[i] = data
					}
					RowsData := temp
					var startRow, endRow, startCol, endCol int
					Normalize(RowsData, &startRow, &endRow, &startCol, &endCol)
					if startRow != 1 && startCol != -1 && endRow != -1 && endCol != -1 {
						fmt.Println("startRow", startRow, "endRow", endRow, "startCol", startCol, "endCol", endCol)
						_, _ = table.toSheet(file, workSheet.Name, RowsData, startRow, endRow, startCol, endCol)
					}
				}
			} else {
				return errors.New("can not load sheet for excel file")
			}
		}
	}

	return nil
}

func readJson(table *MetaTable, file string) error {
	var sheetData []map[string]any
	fileContent, err := os.ReadFile(file)
	if err != nil {
		return err
	}
	err = json.Unmarshal(fileContent, &sheetData)
	if err != nil {
		return err
	}
	if len(sheetData) <= 0 {
		return errors.New("no any json object")
	}
	keyField := ""
	for keyName := range sheetData[0] {
		lowKey := strings.ToLower(keyName)
		if lowKey == "key" {
			keyField = keyName
			break
		} else if lowKey == "id" {
			keyField = keyName
			break
		} else if lowKey == "_key" {
			keyField = keyName
			break
		} else if lowKey == "_id" {
			keyField = keyName
			break
		}
	}
	if len(keyField) <= 0 {
		return errors.New("no any key field for sheet data")
	}
	sheetRows := make(map[string]map[string]any)
	tab := fileName2SheetName(file)
	sheet := &MetaSheet{
		SheetName:  tab,
		OriginFile: file,
		sheetRows:  sheetRows,
		TypeString: nil,
	}
	for i := 0; i < len(sheetData); i++ {
		row := sheetData[i]
		var rowKeyValue string
		switch keyValue := row[keyField].(type) {
		case string:
			rowKeyValue = keyValue
		case int:
			rowKeyValue = strconv.Itoa(keyValue)
		case int64:
			rowKeyValue = strconv.FormatInt(keyValue, 10)
		case json.Number:
			rowKeyValue = keyValue.String()
		case float32:
			rowKeyValue = strconv.FormatFloat(float64(keyValue), 'f', 0, 64)
		case float64:
			rowKeyValue = strconv.FormatFloat(keyValue, 'f', 0, 64)
		default:
			return errors.New(fmt.Sprintf("can not read the row data %d row with key:%s", i, keyField))
		}
		sheetRows[rowKeyValue] = row
	}
	defer table.SheetsMutex.Unlock()
	table.SheetsMutex.Lock()
	table.Sheets[tab] = sheet
	return nil
}
func readCSV(table *MetaTable, file string) error {
	fd, err := os.Open(file)
	if err != nil {
		return err
	}
	defer func(fd *os.File) {
		err := fd.Close()
		if err != nil {

		}
	}(fd)

	reader := csv.NewReader(fd)
	reader.FieldsPerRecord = -1
	reader.LazyQuotes = true
	var records [][]string
	records, err = reader.ReadAll()

	if err != nil {
		return err
	}
	RowsData := make([][]string, 0)

	for _, record := range records {
		Row := make([]string, 0)
		for _, value := range record {
			recordUtf := value
			if !ValidUTF8([]byte(recordUtf)) {

				recordUtf, _, _, _ = gogb2312.ConvertGB2312String(recordUtf)
			}
			recordUtf = strings.TrimSpace(recordUtf)
			Row = append(Row, recordUtf)
		}
		RowsData = append(RowsData, Row)
	}
	var startRow, endRow, startCol, endCol int
	Normalize(RowsData, &startRow, &endRow, &startCol, &endCol)
	if startRow != 1 && startCol != -1 && endRow != -1 && endCol != -1 {
		_, _ = table.toSheet(file, fileName2SheetName(file), RowsData, startRow, endRow, startCol, endCol)
	}
	return nil
}
func fileName2SheetName(file string) string {
	return strings.TrimSuffix(filepath.Base(file), filepath.Ext(file))
}
func readXlsx(table *MetaTable, file string) error {
	xlsx, err := excelize.OpenFile(file)
	if err != nil {
		return err
	}

	sheetMap := xlsx.GetSheetMap()
	for _, sheetName := range sheetMap {
		//可见的sheet才读取
		if xlsx.GetSheetVisible(sheetName) {
			var startRow, endRow, startCol, endCol int
			RowsData := xlsx.GetRows(sheetName)
			Normalize(RowsData, &startRow, &endRow, &startCol, &endCol)
			if startRow != -1 && startCol != -1 && endRow != -1 && endCol != -1 {
				fmt.Println("startRow", startRow, "endRow", endRow, "startCol", startCol, "endCol", endCol)
				_, _ = table.toSheet(file, sheetName, RowsData, startRow, endRow, startCol, endCol)
			}
		}
	}

	return nil
}

func parseTuple(opt *TableOption, appendArray []interface{}, typeString, value string) ([]interface{}, error) {
	switch typeString {
	case "bool":
		if strings.ToLower(value) == "true" {
			return append(appendArray, true), errors.New("can not convert tuple value(bool)")
		} else if strings.ToLower(value) == "false" {
			return append(appendArray, false), errors.New("can not convert tuple value(bool)")
		} else if strings.ToLower(value) == "1" {
			return append(appendArray, true), errors.New("can not convert tuple value(bool)")
		} else if strings.ToLower(value) == "0" {
			return append(appendArray, false), errors.New("can not convert tuple value(bool)")
		} else {
			if intV, err := strconv.ParseInt(value, 10, 32); err == nil {
				return append(appendArray, intV != 0), nil
			} else {
				return nil, errors.New("boolean convert is invalid:" + err.Error())
			}
		}
	case "int":
		outValue, err := strconv.Atoi(value)
		if err != nil {
			return append(appendArray, 0), errors.New("can not convert tuple value(int)")
		} else {
			return append(appendArray, outValue), nil
		}
	case "int32":
		outValue, err := strconv.Atoi(value)
		if err != nil {
			return append(appendArray, 0), errors.New("can not convert tuple value(int32)")
		} else {
			return append(appendArray, outValue), nil
		}
	case "int64":
		outValue, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return append(appendArray, 0), errors.New("can not convert tuple value(int64)")
		} else {
			return append(appendArray, outValue), nil
		}
	case "float":
		outValue, err := strconv.ParseFloat(value, 32)
		if err != nil {
			return append(appendArray, 0), errors.New("can not convert tuple value(float32) or value(float)")
		} else {
			return append(appendArray, outValue), nil
		}
	case "float32":
		outValue, err := strconv.ParseFloat(value, 32)
		if err != nil {
			return append(appendArray, 0), errors.New("can not convert tuple value(float32) or value(float)")
		} else {
			return append(appendArray, outValue), nil
		}
	case "double":
		outValue, err := strconv.ParseFloat(value, 64)
		if err != nil {
			return append(appendArray, 0), errors.New("can not convert tuple value(float64) or value(double)")
		} else {
			return append(appendArray, outValue), nil
		}
	case "float64":
		outValue, err := strconv.ParseFloat(value, 64)
		if err != nil {
			return append(appendArray, 0), errors.New("can not convert tuple value(float64) or value(double)")
		} else {
			return append(appendArray, outValue), nil
		}
	case "string":
		return append(appendArray, value), nil
	default:
		if strings.Index(typeString, "[]") == 0 { // []int ;  [] int64 ; []float ; []flat32 ; []double ; []flat64 ; []string
			tValue, err := splitArray(opt, typeString, value)
			if err == nil {
				return append(appendArray, tValue), nil
			} else {
				return appendArray, err
			}
		}
	}
	return nil, nil
}

/*
*
tuple相关工具类
*/
func getTupleTypes(ctx *TableOption, t string) ([]string, error) {
	const tName = "tuple("
	begin := strings.Index(t, tName)
	if begin != 0 {
		return nil, nil
	}
	end := strings.LastIndex(t, ")")
	if end == -1 && len(t)-1 == end {
		return nil, nil
	}
	content := t[begin+len(tName) : end]
	values := strings.Split(content, ctx.TupleSeparator)

	return values, nil
}

func splitArray(opt *TableOption, itemType, value string) (interface{}, error) {
	values := strings.Split(value, opt.SliceSeparator)
	var errs []error
	outArray := make([]interface{}, 0)
	for i := 0; i < len(values); i++ {
		outValue, err := convertByType(opt, itemType[2:], values[i])
		if err == nil {
			outArray = append(outArray, outValue)
		} else {
			errs = append(errs, err)
		}
	}
	if len(errs) > 0 {
		return outArray, errors.Join(errs...)
	}
	return outArray, nil
}

func splitTuple(opt *TableOption, typeString, value string) (interface{}, error) {
	tupleTypes, err := getTupleTypes(opt, typeString)
	if err != nil {
		return nil, err
	}
	if err != nil {
		return nil, err
	}
	values := strings.Split(value, opt.TupleSeparator)
	var errs []error
	outTuple := make([]interface{}, 0)
	for i := 0; i < len(tupleTypes); i++ {
		outTuple, err = parseTuple(opt, outTuple, tupleTypes[i], values[i])
		errs = append(errs, err)
	}
	if len(errs) > 0 {
		return outTuple, errors.Join(errs...)
	}
	return outTuple, nil
}

func convertByType(opt *TableOption, valueType string, value string) (interface{}, error) {
	switch strings.ToLower(valueType) {
	case "bool":
		if strings.ToLower(value) == "true" {
			return true, nil
		} else if strings.ToLower(value) == "false" {
			return false, nil
		} else if strings.ToLower(value) == "true" {
			return true, nil
		} else if strings.ToLower(value) == "0" {
			return false, nil
		} else {
			if intV, err := strconv.ParseInt(value, 10, 32); err == nil {
				return intV != 0, nil
			} else {
				return false, errors.New("boolean convert is invalid:" + err.Error())
			}
		}
	case "int":
		iValue, _ := strconv.Atoi(value)
		return iValue, nil
	case "int32":
		iValue, _ := strconv.ParseInt(value, 10, 32)
		return int32(iValue), nil
	case "int64":
		iValue, _ := strconv.ParseInt(value, 10, 64)
		return iValue, nil
	case "float":
		fValue, _ := strconv.ParseFloat(value, 32)
		return fValue, nil
	case "float32":
		fValue, _ := strconv.ParseFloat(value, 32)
		return fValue, nil
	case "double":
		fValue, _ := strconv.ParseFloat(value, 64)
		return fValue, nil
	case "float64":
		fValue, _ := strconv.ParseFloat(value, 64)
		return fValue, nil
	case "string":
		return value, nil
	default:
		if strings.Index(valueType, "tuple(") == 0 {
			tValue, err := splitTuple(opt, valueType, value)
			return tValue, err
		} else if strings.Index(valueType, "[]") == 0 { // []int ;  [] int64 ; []float ; []flat32 ; []double ; []flat64 ; []string
			tValue, err := splitArray(opt, valueType, value)
			return tValue, err
		}
	}
	return nil, errors.New(fmt.Sprintf("can not error:%s", valueType))
}

func isCommentString(option *TableOption, commentString string) bool {
	if strings.Index(commentString, option.CommentHeader) == 0 {
		return true
	}

	return false
}

func isTypeString(typeStr string) bool {
	candidate := []string{
		"int", "string", "[]", "tuple", "float", "bool",
	}
	for i := 0; i < len(candidate); i++ {
		if strings.Index(typeStr, candidate[i]) == 0 {
			return true
		}
	}
	return false
}

func (table *MetaTable) toSheet(file, tab string, Rows [][]string, startRow, endRow, startCol, endCol int) (sheet *MetaSheet, err error) {
	valueTypes := Rows[startRow+1]
	//TypeString := make(map[string]string)
	TypeString := make([]string, 0)
	FieldString := make([]string, 0)
	CommentString := make([]string, 0)

	//获取类型字符串
	typeGot := false
	fieldGot := false
	commGot := false
	XlsxRows := make(map[string]map[string]any)
	for i := startRow; i <= endRow; i++ {
		cellString := Rows[i][startCol] //测试第一个可用列
		proc := false
		if !commGot {
			thisComment := isCommentString(&table.Option, cellString)
			if thisComment { //本行为注释类型，并不处理
				for j := startCol; j <= endCol; j++ {
					CommentString = append(CommentString, Rows[i][j])
				}
				commGot = true
				proc = true
			}
		}
		if !typeGot {
			typeGot = isTypeString(cellString)
			if typeGot { //本行为字段的类型
				for j := startCol; j <= endCol; j++ {
					if len(valueTypes[j]) > 0 {
						TypeString = append(TypeString, Rows[i][j])
					}
				}
				proc = true
			}
		}

		if !proc {
			if !fieldGot {
				//获取字段名称
				for j := startCol; j <= endCol; j++ {
					FieldString = append(FieldString, Rows[i][j])
				}
				proc = true
				fieldGot = true
			}
		}
		if !proc && fieldGot && typeGot { //类型和字段名均已经获取
			thisLineComment := false                                  //只要本行的Row有Cell是注释，直接忽略本行
			Row := make(map[string]any)                               //构建行数据
			for j := startCol; j <= endCol && !thisLineComment; j++ { //遍历检查各个字段
				if len(valueTypes[j]) > 0 && len(FieldString[j]) > 0 { //字段和类型有效
					cell := Rows[i][j] //测试第一个可用列
					if len(cell) != 0 {
						if isCommentString(&table.Option, cell) {
							thisLineComment = true //忽略
							break                  //跳出本行的字段循环
						}
						Row[FieldString[j]], err = convertByType(&table.Option, TypeString[j], Rows[i][j])
						if err != nil {
							fmt.Printf("there is an error at convert type:%s cell(%d,%d)", TypeString[i], i, j)
						}
					}
				}
			}
			keyOrId := Rows[i][startCol]
			XlsxRows[keyOrId] = Row
		}
	}

	sheet = &MetaSheet{
		SheetName:     tab,
		OriginFile:    file,
		sheetRows:     XlsxRows,
		TypeString:    TypeString,
		FieldString:   FieldString,
		CommentString: CommentString,
	}

	defer table.SheetsMutex.Unlock()
	table.SheetsMutex.Lock()
	table.Sheets[tab] = sheet
	return sheet, nil
}

func Normalize(input [][]string, startRow *int, endRow *int, startCol *int, endCol *int) {
	sr := -1
	er := -1
	sc := -1
	ec := -1
	for i := 0; i < len(input); i++ {
		for j := 0; j < len(input[i]); j++ {
			sl := len(input[i][j])
			if sr == -1 {
				if sl != 0 {
					sr = i
					er = i
				}
			} else {
				if sl != 0 {
					er = i
				}
			}

			if sc == -1 {
				if sl != 0 {
					sc = j
					ec = j
				}
			} else {
				if sl != 0 && ec < j {
					ec = j
				}
			}
		}
	}
	*startRow = sr
	*endRow = er

	*startCol = sc
	*endCol = ec
}
func ValidUTF8(buf []byte) bool {
	nBytes := 0
	for i := 0; i < len(buf); i++ {
		if nBytes == 0 {
			if (buf[i] & 0x80) != 0 { //与操作之后不为0，说明首位为1
				for (buf[i] & 0x80) != 0 {
					buf[i] <<= 1 //左移一位
					nBytes++     //记录字符共占几个字节
				}

				if nBytes < 2 || nBytes > 6 { //因为UTF8编码单字符最多不超过6个字节
					return false
				}

				nBytes-- //减掉首字节的一个计数
			}
		} else { //处理多字节字符
			if buf[i]&0xc0 != 0x80 { //判断多字节后面的字节是否是10开头
				return false
			}
			nBytes--
		}
	}
	return nBytes == 0
}

func (table *MetaTable) GetSheetNames() []string {
	if table == nil {
		return nil
	}
	defer table.SheetsMutex.RUnlock()
	table.SheetsMutex.RLock()
	retNames := make([]string, 0)
	for tabName := range table.Sheets {
		retNames = append(retNames, tabName)
	}
	return retNames
}

func (table *MetaTable) GetSheet(tab string) *MetaSheet {
	if table == nil {
		return nil
	}

	defer table.SheetsMutex.RUnlock()
	table.SheetsMutex.RLock()

	if sheet, ok := table.Sheets[tab]; ok {
		return sheet
	}
	return nil
}

func (sheet *MetaSheet) GetRowCount() (int, error) {
	if sheet == nil {
		return 0, errors.New("invalidate sheet")
	}

	defer sheet.sheetMutex.RUnlock()
	sheet.sheetMutex.RLock()

	return len(sheet.sheetRows), nil
}

func (sheet *MetaSheet) GetRowKeys() ([]string, error) {
	if sheet == nil {
		return nil, errors.New("invalidate sheet")
	}

	defer sheet.sheetMutex.RUnlock()
	sheet.sheetMutex.RLock()
	keys := make([]string, 0)
	for key := range sheet.sheetRows {
		keys = append(keys, key)
	}

	return keys, nil
}
func (sheet *MetaSheet) GetFields() ([]string, error) {
	if sheet == nil {
		return nil, errors.New("invalidate sheet")
	}

	defer sheet.sheetMutex.RUnlock()
	sheet.sheetMutex.RLock()
	return sheet.FieldString, nil
}
func (sheet *MetaSheet) GetComments() ([]string, error) {
	if sheet == nil {
		return nil, errors.New("invalidate sheet")
	}

	defer sheet.sheetMutex.RUnlock()
	sheet.sheetMutex.RLock()
	return sheet.CommentString, nil
}

// GetRowObject /*输出的结构成员必须public，否则解析失败
func (sheet *MetaSheet) GetRowObject(key string, outObject interface{}) error {
	outMap, err := sheet.getRow(key)
	if err != nil {
		return err
	}
	return mapstructure.Decode(outMap, outObject)
}

func (sheet *MetaSheet) getRow(key string) (map[string]any, error) {
	if sheet == nil {
		return nil, errors.New("invalidate sheet")
	}

	defer sheet.sheetMutex.RUnlock()
	sheet.sheetMutex.RLock()

	if value, ok := sheet.sheetRows[key]; ok {
		return value, nil
	}
	return nil, errors.New("no this key:" + key)
}

func (sheet *MetaSheet) GetCellObject(key string, field string, outObject interface{}) (err error) {
	if sheet == nil {
		return errors.New("invalidate sheet")
	}

	defer sheet.sheetMutex.RUnlock()
	sheet.sheetMutex.RLock()

	rowValue, err := sheet.getRow(key)
	if err != nil {
		return err
	}

	if cellValue, ok := rowValue[field]; ok {
		return mapstructure.Decode(cellValue, outObject)
	}
	return errors.New("no this key:" + key)
}
func (sheet *MetaSheet) GetCellObjectByIndex(key string, fieldIndex int, outObject interface{}) (err error) {
	if sheet == nil {
		return errors.New("invalidate sheet")
	}

	defer sheet.sheetMutex.RUnlock()
	sheet.sheetMutex.RLock()
	if fieldIndex >= len(sheet.FieldString) || fieldIndex < 0 {
		return errors.New("field index out of range")
	}

	field := sheet.FieldString[fieldIndex]
	if cellValue, ok := sheet.sheetRows[key][field]; ok {
		return mapstructure.Decode(cellValue, outObject)
	}
	return sheet.GetCellObject(key, field, outObject)
}
